package com.yuan.yblog.common.service;

import cn.hutool.core.util.DesensitizedUtil;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * 缓存crud服务类
 */
@Service
public class EntityCacheService{

    @Autowired
    RedisService redisService;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 默认过期时间，单位：秒
     */
    private static final long DEFAULT_EXPIRE = 60 * 60;

    /**
     * 获取缓存，刷新指定有效时间
     * @param keyPrefix key前缀
     * @param id 实体id
     */
    public Object getEntity(String keyPrefix, Object id, Long refreshTime) {
        String key = keyPrefix + id;
        if (!redisService.hasKey(key)) {
            return null;
        }
        // 有人访问，刷新缓存有效期
        refresh(keyPrefix,id,refreshTime);
        return redisService.get(key);
    }

    /**
     * 插入缓存
     * @param keyPrefix key前缀
     * @param object 实体
     */
    public void setEntity(String keyPrefix, Object id, Object object, Long expire) {
        String key = keyPrefix + id;
        if (object == null) return;
        // 添加随机失效时间值，防止缓存雪崩，非空对象有效时间为24h以上
        redisService.set(key,object,expire+new Random().nextInt(Math.toIntExact(expire))+1);
    }

    /**
     * 缓存空对象，预防缓存穿透，空对象有效期 150-400s
     * @param keyPrefix key前缀
     * @param id 目标id
     * @param clazz 类
     */
    public Object setEmptyEntity(String keyPrefix, Object id, Class clazz) {
        // 构造key
        String key = keyPrefix + id;
        Object emptyObject = null;
        try {
            emptyObject = clazz.newInstance();/*构造空对象*/
            Method setId = clazz.getMethod("setId",Long.class);/*获取set方法*/
            setId.invoke(emptyObject,id);/*运行set方法*/
            //空对象持续：150s左右
            redisService.set(key,emptyObject,150 + new Random().nextInt(250)+1);/*将空对象返回*/
        } catch (InstantiationException
                 | InvocationTargetException
                 | NoSuchMethodException
                 | IllegalAccessException e) {
            e.printStackTrace();
        }
        return emptyObject;
    }

    /**
     * 删除缓存
     * @param keyPrefix key前缀
     * @param id 实体标识
     */
    public void deleteEntity(String keyPrefix, Object id) {
        String key = keyPrefix + id;
        if (!redisService.hasKey(key)) {
            return;
        }
        redisService.del(key);
    }

    /**
     * 刷新过期时间
     * @param keyPrefix key前缀
     * @param id 实体标识
     */
    private void refresh(String keyPrefix, Object id, Long expire) {
        String key = keyPrefix + id;
        if (!redisService.hasKey(key)) {
            return;
        }
        if (expire == null) {
            expire = DEFAULT_EXPIRE;
        }
        // 增加随机时间
        redisService.expire(key,expire+new Random().nextInt(Math.toIntExact(expire))+1);
    }

    /**
     * 根据id查询实体，先查 Cache，再查 DB
     * @param clazz 实例类型
     * @param entityId 实例id
     * @param redisKey 实例缓存key
     * @param redisKeyExpire 实例key继续有效的时长，单位：秒
     * @param lockKey 锁key
     * @param lockWaitTime 可等待锁的最长时间
     * @param entityMapper 查库 mapper
     * @return 实例 T
     */
    public <T> T selectById(Class<T> clazz, Long entityId, String redisKey, Long redisKeyExpire, String lockKey, Long lockWaitTime, BaseMapper<T> entityMapper) {
        // 先查缓存
        T entity = (T) getEntity(redisKey, entityId, redisKeyExpire);
        if(entity != null) {
            return (T) entity;
        }
        // 缓存没有，去取锁后查库
        RLock redisLock = redissonClient.getLock(lockKey + entityId);
        boolean isLock = false;
        // 取锁
        try {
            isLock = redisLock.tryLock(lockWaitTime, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 递归获取结果
        if(!isLock){
            return selectById(clazz, entityId, redisKey, redisKeyExpire, lockKey, lockWaitTime, entityMapper);
        }
        // 查询数据库
        T result = entityMapper.selectById(entityId);
        try {
            if(result==null){
                // 可能是无效请求，考虑缓存空对象
                result = (T) setEmptyEntity(redisKey,entityId,clazz);
            }
            else {
                // 缓存实体
                setEntity(redisKey, entityId, result, redisKeyExpire);
            }
        } finally {
            // 释锁
            redisLock.unlock();
        }
        //结束
        return result;
    }



}
